// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <memory>
#include <string>
#include <type_traits>
#include <vector>

#include <nebula/types/type.h>
#include <nebula/types/type_id_traits.h>
#include <nebula/bits/bit_util.h>

namespace nebula {


    //
    // Per-type type traits
    //

    /// \addtogroup type-traits
    /// \brief Base template for type traits of Nebula data types
    /// Type traits provide various information about a type at compile time, such
    /// as the associated ArrayType, BuilderType, and ScalarType. Not all types
    /// provide all information.
    /// \tparam T An Nebula data type
    template<typename T>
    struct TypeTraits {
    };

    /// \brief Base template for type traits of C++ types
    /// \tparam T A standard C++ type
    template<typename T>
    struct CTypeTraits {
    };

    /// \addtogroup type-traits
    /// @{
    template<>
    struct TypeTraits<NullType> {
        using ArrayType = NullArray;
        using BuilderType = NullBuilder;
        using ScalarType = NullScalar;

        static constexpr int64_t bytes_required(int64_t) { return 0; }

        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return null(); }
    };

    template<>
    struct TypeTraits<BooleanType> {
        using ArrayType = BooleanArray;
        using BuilderType = BooleanBuilder;
        using ScalarType = BooleanScalar;
        using CType = bool;

        static constexpr int64_t bytes_required(int64_t elements) {
            return bit_util::BytesForBits(elements);
        }

        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return boolean(); }
    };

    /// @}

    /// \addtogroup c-type-traits
    template<>
    struct CTypeTraits<bool> : public TypeTraits<BooleanType> {
        using ArrowType = BooleanType;
    };

    template<>
    struct TypeTraits<UInt8Type> {
        using ArrayType = UInt8Array;
        using BuilderType = UInt8Builder;
        using ScalarType = UInt8Scalar;
        using TensorType = UInt8Tensor;
        using CType = UInt8Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return uint8(); }
    };

    template<>
    struct CTypeTraits<uint8_t> : public TypeTraits<UInt8Type> {
        using ArrowType = UInt8Type;
    };

    template<>
    struct TypeTraits<Int8Type> {
        using ArrayType = Int8Array;
        using BuilderType = Int8Builder;
        using ScalarType = Int8Scalar;
        using TensorType = Int8Tensor;
        using CType = Int8Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return int8(); }
    };

    template<>
    struct CTypeTraits<int8_t> : public TypeTraits<Int8Type> {
        using ArrowType = Int8Type;
    };

    template<>
    struct TypeTraits<UInt16Type> {
        using ArrayType = UInt16Array;
        using BuilderType = UInt16Builder;
        using ScalarType = UInt16Scalar;
        using TensorType = UInt16Tensor;
        using CType = UInt16Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return uint16(); }
    };

    template<>
    struct CTypeTraits<uint16_t> : public TypeTraits<UInt16Type> {
        using ArrowType = UInt16Type;
    };

    template<>
    struct TypeTraits<Int16Type> {
        using ArrayType = Int16Array;
        using BuilderType = Int16Builder;
        using ScalarType = Int16Scalar;
        using TensorType = Int16Tensor;
        using CType = Int16Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return int16(); }
    };

    template<>
    struct CTypeTraits<int16_t> : public TypeTraits<Int16Type> {
        using ArrowType = Int16Type;
    };

    template<>
    struct TypeTraits<UInt32Type> {
        using ArrayType = UInt32Array;
        using BuilderType = UInt32Builder;
        using ScalarType = UInt32Scalar;
        using TensorType = UInt32Tensor;
        using CType = UInt32Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return uint32(); }
    };

    template<>
    struct CTypeTraits<uint32_t> : public TypeTraits<UInt32Type> {
        using ArrowType = UInt32Type;
    };

    template<>
    struct TypeTraits<Int32Type> {
        using ArrayType = Int32Array;
        using BuilderType = Int32Builder;
        using ScalarType = Int32Scalar;
        using TensorType = Int32Tensor;
        using CType = Int32Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return int32(); }
    };

    template<>
    struct CTypeTraits<int32_t> : public TypeTraits<Int32Type> {
        using ArrowType = Int32Type;
    };

    template<>
    struct TypeTraits<UInt64Type> {
        using ArrayType = UInt64Array;
        using BuilderType = UInt64Builder;
        using ScalarType = UInt64Scalar;
        using TensorType = UInt64Tensor;
        using CType = UInt64Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return uint64(); }
    };

    template<>
    struct CTypeTraits<uint64_t> : public TypeTraits<UInt64Type> {
        using ArrowType = UInt64Type;
    };

    template<>
    struct TypeTraits<Int64Type> {
        using ArrayType = Int64Array;
        using BuilderType = Int64Builder;
        using ScalarType = Int64Scalar;
        using TensorType = Int64Tensor;
        using CType = Int64Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return int64(); }
    };

    template<>
    struct CTypeTraits<int64_t> : public TypeTraits<Int64Type> {
        using ArrowType = Int64Type;
    };

    template<>
    struct TypeTraits<Fp32Type> {
        using ArrayType = Fp32Array;
        using BuilderType = Fp32Builder;
        using ScalarType = Fp32Scalar;
        using TensorType = Fp32Tensor;
        using CType = Fp32Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return float32(); }
    };

    template<>
    struct CTypeTraits<float> : public TypeTraits<Fp32Type> {
        using ArrowType = Fp32Type;
    };

    template<>
    struct TypeTraits<Fp64Type> {
        using ArrayType = Fp64Array;
        using BuilderType = Fp64Builder;
        using ScalarType = Fp64Scalar;
        using TensorType = Fp64Tensor;
        using CType = Fp64Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(CType));
        }

        constexpr static bool is_parameter_free = true;
        static inline std::shared_ptr<DataType> type_singleton() { return float64(); }
    };

    template<>
    struct CTypeTraits<double> : public TypeTraits<Fp64Type> {
        using ArrowType = Fp64Type;
    };


    /// \addtogroup type-traits
    /// @{
    template<>
    struct TypeTraits<Date64Type> {
        using ArrayType = Date64Array;
        using BuilderType = Date64Builder;
        using ScalarType = Date64Scalar;
        using CType = Date64Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int64_t));
        }

        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return date64(); }
    };

    template<>
    struct TypeTraits<Date32Type> {
        using ArrayType = Date32Array;
        using BuilderType = Date32Builder;
        using ScalarType = Date32Scalar;
        using CType = Date32Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int32_t));
        }

        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return date32(); }
    };

    template<>
    struct TypeTraits<TimestampType> {
        using ArrayType = TimestampArray;
        using BuilderType = TimestampBuilder;
        using ScalarType = TimestampScalar;
        using CType = TimestampType::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int64_t));
        }

        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<DurationType> {
        using ArrayType = DurationArray;
        using BuilderType = DurationBuilder;
        using ScalarType = DurationScalar;
        using CType = DurationType::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int64_t));
        }

        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<DayTimeIntervalType> {
        using ArrayType = DayTimeIntervalArray;
        using BuilderType = DayTimeIntervalBuilder;
        using ScalarType = DayTimeIntervalScalar;
        using CType = DayTimeIntervalType::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(DayTimeIntervalType::DayMilliseconds));
        }

        constexpr static bool is_parameter_free = true;

        static std::shared_ptr<DataType> type_singleton() { return day_time_interval(); }
    };

    template<>
    struct TypeTraits<MonthDayNanoIntervalType> {
        using ArrayType = MonthDayNanoIntervalArray;
        using BuilderType = MonthDayNanoIntervalBuilder;
        using ScalarType = MonthDayNanoIntervalScalar;
        using CType = MonthDayNanoIntervalType::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements *
                   static_cast<int64_t>(sizeof(MonthDayNanoIntervalType::MonthDayNanos));
        }

        constexpr static bool is_parameter_free = true;

        static std::shared_ptr<DataType> type_singleton() { return month_day_nano_interval(); }
    };

    template<>
    struct TypeTraits<MonthIntervalType> {
        using ArrayType = MonthIntervalArray;
        using BuilderType = MonthIntervalBuilder;
        using ScalarType = MonthIntervalScalar;
        using CType = MonthIntervalType::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int32_t));
        }

        constexpr static bool is_parameter_free = true;

        static std::shared_ptr<DataType> type_singleton() { return month_interval(); }
    };

    template<>
    struct TypeTraits<Time32Type> {
        using ArrayType = Time32Array;
        using BuilderType = Time32Builder;
        using ScalarType = Time32Scalar;
        using CType = Time32Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int32_t));
        }

        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<Time64Type> {
        using ArrayType = Time64Array;
        using BuilderType = Time64Builder;
        using ScalarType = Time64Scalar;
        using CType = Time64Type::c_type;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(int64_t));
        }

        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<Fp16Type> {
        using ArrayType = Fp16Array;
        using BuilderType = Fp16Builder;
        using ScalarType = Fp16Scalar;
        using TensorType = Fp16Tensor;
        using CType = uint16_t;

        static constexpr int64_t bytes_required(int64_t elements) {
            return elements * static_cast<int64_t>(sizeof(uint16_t));
        }

        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return float16(); }
    };

    template<>
    struct TypeTraits<Decimal128Type> {
        using ArrayType = Decimal128Array;
        using BuilderType = Decimal128Builder;
        using ScalarType = Decimal128Scalar;
        using CType = Decimal128;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<Decimal256Type> {
        using ArrayType = Decimal256Array;
        using BuilderType = Decimal256Builder;
        using ScalarType = Decimal256Scalar;
        using CType = Decimal256;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<BinaryType> {
        using ArrayType = BinaryArray;
        using BuilderType = BinaryBuilder;
        using ScalarType = BinaryScalar;
        using OffsetType = Int32Type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return binary(); }
    };

    template<>
    struct TypeTraits<BinaryViewType> {
        using ArrayType = BinaryViewArray;
        using BuilderType = BinaryViewBuilder;
        using ScalarType = BinaryViewScalar;
        using CType = BinaryViewType::c_type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return binary_view(); }
    };

    template<>
    struct TypeTraits<LargeBinaryType> {
        using ArrayType = LargeBinaryArray;
        using BuilderType = LargeBinaryBuilder;
        using ScalarType = LargeBinaryScalar;
        using OffsetType = Int64Type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return large_binary(); }
    };

    template<>
    struct TypeTraits<FixedSizeBinaryType> {
        using ArrayType = FixedSizeBinaryArray;
        using BuilderType = FixedSizeBinaryBuilder;
        using ScalarType = FixedSizeBinaryScalar;
        // FixedSizeBinary doesn't have offsets per se, but string length is int32 sized
        using OffsetType = Int32Type;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<StringType> {
        using ArrayType = StringArray;
        using BuilderType = StringBuilder;
        using ScalarType = StringScalar;
        using OffsetType = Int32Type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return utf8(); }
    };

    template<>
    struct TypeTraits<StringViewType> {
        using ArrayType = StringViewArray;
        using BuilderType = StringViewBuilder;
        using ScalarType = StringViewScalar;
        using CType = BinaryViewType::c_type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return utf8_view(); }
    };

    template<>
    struct TypeTraits<LargeStringType> {
        using ArrayType = LargeStringArray;
        using BuilderType = LargeStringBuilder;
        using ScalarType = LargeStringScalar;
        using OffsetType = Int64Type;
        constexpr static bool is_parameter_free = true;

        static inline std::shared_ptr<DataType> type_singleton() { return large_utf8(); }
    };

    template<>
    struct TypeTraits<RunEndEncodedType> {
        using ArrayType = RunEndEncodedArray;
        using BuilderType = RunEndEncodedBuilder;
        using ScalarType = RunEndEncodedScalar;

        constexpr static bool is_parameter_free = false;
    };

    /// @}

    /// \addtogroup c-type-traits
    /// @{
    template<>
    struct CTypeTraits<std::string> : public TypeTraits<StringType> {
        using ArrowType = StringType;
    };

    template<>
    struct CTypeTraits<BinaryViewType::c_type> : public TypeTraits<BinaryViewType> {
        using ArrowType = BinaryViewType;
    };

    template<>
    struct CTypeTraits<const char *> : public CTypeTraits<std::string> {
    };

    template<size_t N>
    struct CTypeTraits<const char (&)[N]> : public CTypeTraits<std::string> {
    };

    template<>
    struct CTypeTraits<DayTimeIntervalType::DayMilliseconds>
            : public TypeTraits<DayTimeIntervalType> {
        using ArrowType = DayTimeIntervalType;
    };

    /// @}

    /// \addtogroup type-traits
    /// @{
    template<>
    struct TypeTraits<ListType> {
        using ArrayType = ListArray;
        using BuilderType = ListBuilder;
        using ScalarType = ListScalar;
        using OffsetType = Int32Type;
        using OffsetArrayType = Int32Array;
        using OffsetBuilderType = Int32Builder;
        using OffsetScalarType = Int32Scalar;
        constexpr static bool is_parameter_free = false;
        using LargeType = LargeListType;
    };

    template<>
    struct TypeTraits<LargeListType> {
        using ArrayType = LargeListArray;
        using BuilderType = LargeListBuilder;
        using ScalarType = LargeListScalar;
        using OffsetType = Int64Type;
        using OffsetArrayType = Int64Array;
        using OffsetBuilderType = Int64Builder;
        using OffsetScalarType = Int64Scalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<ListViewType> {
        using ArrayType = ListViewArray;
        using BuilderType = ListViewBuilder;
        using ScalarType = ListViewScalar;
        using OffsetType = Int32Type;
        using OffsetArrayType = Int32Array;
        using OffsetBuilderType = Int32Builder;
        using OffsetScalarType = Int32Scalar;
        constexpr static bool is_parameter_free = false;
        using LargeType = LargeListViewType;
    };

    template<>
    struct TypeTraits<LargeListViewType> {
        using ArrayType = LargeListViewArray;
        using BuilderType = LargeListViewBuilder;
        using ScalarType = LargeListViewScalar;
        using OffsetType = Int64Type;
        using OffsetArrayType = Int64Array;
        using OffsetBuilderType = Int64Builder;
        using OffsetScalarType = Int64Scalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<MapType> {
        using ArrayType = MapArray;
        using BuilderType = MapBuilder;
        using ScalarType = MapScalar;
        using OffsetType = Int32Type;
        using OffsetArrayType = Int32Array;
        using OffsetBuilderType = Int32Builder;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<FixedSizeListType> {
        using ArrayType = FixedSizeListArray;
        using BuilderType = FixedSizeListBuilder;
        using ScalarType = FixedSizeListScalar;
        constexpr static bool is_parameter_free = false;
    };

    /// @}

    /// \addtogroup c-type-traits
    template<typename CType>
    struct CTypeTraits<std::vector<CType> > : public TypeTraits<ListType> {
        using ArrowType = ListType;

        static inline std::shared_ptr<DataType> type_singleton() {
            return list(CTypeTraits<CType>::type_singleton());
        }
    };

    /// \addtogroup type-traits
    /// @{
    template<>
    struct TypeTraits<StructType> {
        using ArrayType = StructArray;
        using BuilderType = StructBuilder;
        using ScalarType = StructScalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<SparseUnionType> {
        using ArrayType = SparseUnionArray;
        using BuilderType = SparseUnionBuilder;
        using ScalarType = SparseUnionScalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<DenseUnionType> {
        using ArrayType = DenseUnionArray;
        using BuilderType = DenseUnionBuilder;
        using ScalarType = DenseUnionScalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<DictionaryType> {
        using ArrayType = DictionaryArray;
        using ScalarType = DictionaryScalar;
        constexpr static bool is_parameter_free = false;
    };

    template<>
    struct TypeTraits<ExtensionType> {
        using ArrayType = ExtensionArray;
        using ScalarType = ExtensionScalar;
        constexpr static bool is_parameter_free = false;
    };

    /// @}

    namespace internal {
        template<typename... Ts>
        struct make_void {
            using type = void;
        };

        template<typename... Ts>
        using void_t = typename make_void<Ts...>::type;
    } // namespace internal

    //
    // Useful type predicates
    //

    /// \addtogroup type-predicates
    /// @{

    // only in C++14
    template<bool B, typename T = void>
    using enable_if_t = typename std::enable_if<B, T>::type;

    template<typename T>
    using is_null_type = std::is_same<NullType, T>;

    template<typename T, typename R = void>
    using enable_if_null = enable_if_t<is_null_type<T>::value, R>;

    template<typename T>
    using is_boolean_type = std::is_same<BooleanType, T>;

    template<typename T, typename R = void>
    using enable_if_boolean = enable_if_t<is_boolean_type<T>::value, R>;

    template<typename T>
    using is_number_type = std::is_base_of<NumberType, T>;

    template<typename T, typename R = void>
    using enable_if_number = enable_if_t<is_number_type<T>::value, R>;

    template<typename T>
    using is_integer_type = std::is_base_of<IntegerType, T>;

    template<typename T, typename R = void>
    using enable_if_integer = enable_if_t<is_integer_type<T>::value, R>;

    template<typename T>
    using is_signed_integer_type =
    std::integral_constant<bool, is_integer_type<T>::value &&
                                 std::is_signed<typename T::c_type>::value>;

    template<typename T, typename R = void>
    using enable_if_signed_integer = enable_if_t<is_signed_integer_type<T>::value, R>;

    template<typename T>
    using is_unsigned_integer_type =
    std::integral_constant<bool, is_integer_type<T>::value &&
                                 std::is_unsigned<typename T::c_type>::value>;

    template<typename T, typename R = void>
    using enable_if_unsigned_integer = enable_if_t<is_unsigned_integer_type<T>::value, R>;

    // Note this will also include Fp16Type which is represented by a
    // non-floating point primitive (uint16_t).
    template<typename T>
    using is_floating_type = std::is_base_of<FloatingPointType, T>;

    template<typename T, typename R = void>
    using enable_if_floating_point = enable_if_t<is_floating_type<T>::value, R>;

    // Half floats are special in that they behave physically like an unsigned
    // integer.
    template<typename T>
    using is_half_float_type = std::is_same<Fp16Type, T>;

    template<typename T, typename R = void>
    using enable_if_half_float = enable_if_t<is_half_float_type<T>::value, R>;

    // Binary Types

    // Base binary refers to Binary/LargeBinary/String/LargeString
    template<typename T>
    using is_base_binary_type = std::is_base_of<BaseBinaryType, T>;

    template<typename T, typename R = void>
    using enable_if_base_binary = enable_if_t<is_base_binary_type<T>::value, R>;

    // Any binary excludes string from Base binary
    template<typename T>
    using is_binary_type =
    std::integral_constant<bool, std::is_same<BinaryType, T>::value ||
                                 std::is_same<LargeBinaryType, T>::value>;

    template<typename T, typename R = void>
    using enable_if_binary = enable_if_t<is_binary_type<T>::value, R>;

    template<typename T>
    using is_string_type =
    std::integral_constant<bool, std::is_same<StringType, T>::value ||
                                 std::is_same<LargeStringType, T>::value>;

    template<typename T, typename R = void>
    using enable_if_string = enable_if_t<is_string_type<T>::value, R>;

    template<typename T>
    using is_binary_view_like_type = std::is_base_of<BinaryViewType, T>;

    template<typename T>
    using is_binary_view_type = std::is_same<BinaryViewType, T>;

    template<typename T>
    using is_string_view_type = std::is_same<StringViewType, T>;

    template<typename T, typename R = void>
    using enable_if_binary_view_like = enable_if_t<is_binary_view_like_type<T>::value, R>;

    template<typename T, typename R = void>
    using enable_if_binary_view = enable_if_t<is_binary_view_type<T>::value, R>;

    template<typename T, typename R = void>
    using enable_if_string_view = enable_if_t<is_string_view_type<T>::value, R>;

    template<typename T>
    using is_string_like_type =
    std::integral_constant<bool, is_base_binary_type<T>::value && T::is_utf8>;

    template<typename T, typename R = void>
    using enable_if_string_like = enable_if_t<is_string_like_type<T>::value, R>;

    template<typename T, typename U, typename R = void>
    using enable_if_same = enable_if_t<std::is_same<T, U>::value, R>;

    // Note that this also includes DecimalType
    template<typename T>
    using is_fixed_size_binary_type = std::is_base_of<FixedSizeBinaryType, T>;

    template<typename T, typename R = void>
    using enable_if_fixed_size_binary = enable_if_t<is_fixed_size_binary_type<T>::value, R>;

    // This includes primitive, dictionary, and fixed-size-binary types
    template<typename T>
    using is_fixed_width_type = std::is_base_of<FixedWidthType, T>;

    template<typename T, typename R = void>
    using enable_if_fixed_width_type = enable_if_t<is_fixed_width_type<T>::value, R>;

    template<typename T>
    using is_binary_like_type =
    std::integral_constant<bool, (is_base_binary_type<T>::value &&
                                  !is_string_like_type<T>::value) ||
                                 is_fixed_size_binary_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_binary_like = enable_if_t<is_binary_like_type<T>::value, R>;

    template<typename T>
    using is_decimal_type = std::is_base_of<DecimalType, T>;

    template<typename T, typename R = void>
    using enable_if_decimal = enable_if_t<is_decimal_type<T>::value, R>;

    template<typename T>
    using is_decimal128_type = std::is_base_of<Decimal128Type, T>;

    template<typename T, typename R = void>
    using enable_if_decimal128 = enable_if_t<is_decimal128_type<T>::value, R>;

    template<typename T>
    using is_decimal256_type = std::is_base_of<Decimal256Type, T>;

    template<typename T, typename R = void>
    using enable_if_decimal256 = enable_if_t<is_decimal256_type<T>::value, R>;

    // Nested Types

    template<typename T>
    using is_nested_type = std::is_base_of<NestedType, T>;

    template<typename T, typename R = void>
    using enable_if_nested = enable_if_t<is_nested_type<T>::value, R>;

    template<typename T, typename R = void>
    using enable_if_not_nested = enable_if_t<!is_nested_type<T>::value, R>;

    template<typename T>
    using is_var_length_list_type =
    std::integral_constant<bool, std::is_base_of<LargeListType, T>::value ||
                                 std::is_base_of<ListType, T>::value>;

    template<typename T, typename R = void>
    using enable_if_var_size_list = enable_if_t<is_var_length_list_type<T>::value, R>;

    // DEPRECATED use is_var_length_list_type.
    template<typename T>
    using is_base_list_type = is_var_length_list_type<T>;

    // DEPRECATED use enable_if_var_size_list
    template<typename T, typename R = void>
    using enable_if_base_list = enable_if_var_size_list<T, R>;

    template<typename T>
    using is_fixed_size_list_type = std::is_same<FixedSizeListType, T>;

    template<typename T, typename R = void>
    using enable_if_fixed_size_list = enable_if_t<is_fixed_size_list_type<T>::value, R>;

    template<typename T>
    using is_list_type =
    std::integral_constant<bool, std::is_same<T, ListType>::value ||
                                 std::is_same<T, LargeListType>::value ||
                                 std::is_same<T, FixedSizeListType>::value>;

    template<typename T, typename R = void>
    using enable_if_list_type = enable_if_t<is_list_type<T>::value, R>;

    template<typename T>
    using is_list_view_type =
    std::disjunction<std::is_same<T, ListViewType>, std::is_same<T, LargeListViewType> >;

    template<typename T, typename R = void>
    using enable_if_list_view = enable_if_t<is_list_view_type<T>::value, R>;

    template<typename T>
    using is_list_like_type =
    std::integral_constant<bool, is_var_length_list_type<T>::value ||
                                 is_fixed_size_list_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_list_like = enable_if_t<is_list_like_type<T>::value, R>;

    template<typename T>
    using is_var_length_list_like_type =
    std::disjunction<is_var_length_list_type<T>, is_list_view_type<T> >;

    template<typename T, typename R = void>
    using enable_if_var_length_list_like =
    enable_if_t<is_var_length_list_like_type<T>::value, R>;

    template<typename T>
    using is_struct_type = std::is_base_of<StructType, T>;

    template<typename T, typename R = void>
    using enable_if_struct = enable_if_t<is_struct_type<T>::value, R>;

    template<typename T>
    using is_union_type = std::is_base_of<UnionType, T>;

    template<typename T, typename R = void>
    using enable_if_union = enable_if_t<is_union_type<T>::value, R>;

    // temporal_types

    template<typename T>
    using is_temporal_type = std::is_base_of<TemporalType, T>;

    template<typename T, typename R = void>
    using enable_if_temporal = enable_if_t<is_temporal_type<T>::value, R>;

    template<typename T>
    using is_date_type = std::is_base_of<DateType, T>;

    template<typename T, typename R = void>
    using enable_if_date = enable_if_t<is_date_type<T>::value, R>;

    template<typename T>
    using is_time_type = std::is_base_of<TimeType, T>;

    template<typename T, typename R = void>
    using enable_if_time = enable_if_t<is_time_type<T>::value, R>;

    template<typename T>
    using is_timestamp_type = std::is_base_of<TimestampType, T>;

    template<typename T, typename R = void>
    using enable_if_timestamp = enable_if_t<is_timestamp_type<T>::value, R>;

    template<typename T>
    using is_duration_type = std::is_base_of<DurationType, T>;

    template<typename T, typename R = void>
    using enable_if_duration = enable_if_t<is_duration_type<T>::value, R>;

    template<typename T>
    using is_interval_type = std::is_base_of<IntervalType, T>;

    template<typename T, typename R = void>
    using enable_if_interval = enable_if_t<is_interval_type<T>::value, R>;

    template<typename T>
    using is_run_end_encoded_type = std::is_base_of<RunEndEncodedType, T>;

    template<typename T, typename R = void>
    using enable_if_run_end_encoded = enable_if_t<is_run_end_encoded_type<T>::value, R>;

    template<typename T>
    using is_dictionary_type = std::is_base_of<DictionaryType, T>;

    template<typename T, typename R = void>
    using enable_if_dictionary = enable_if_t<is_dictionary_type<T>::value, R>;

    template<typename T>
    using is_extension_type = std::is_base_of<ExtensionType, T>;

    template<typename T, typename R = void>
    using enable_if_extension = enable_if_t<is_extension_type<T>::value, R>;

    // Attribute differentiation

    template<typename T>
    using is_primitive_ctype = std::is_base_of<PrimitiveCType, T>;

    template<typename T, typename R = void>
    using enable_if_primitive_ctype = enable_if_t<is_primitive_ctype<T>::value, R>;

    template<typename T>
    using has_c_type = std::integral_constant<bool, is_primitive_ctype<T>::value ||
                                                    is_temporal_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_has_c_type = enable_if_t<has_c_type<T>::value, R>;

    template<typename T>
    using has_string_view =
    std::integral_constant<bool, std::is_same<BinaryType, T>::value ||
                                 std::is_same<BinaryViewType, T>::value ||
                                 std::is_same<LargeBinaryType, T>::value ||
                                 std::is_same<StringType, T>::value ||
                                 std::is_same<StringViewType, T>::value ||
                                 std::is_same<LargeStringType, T>::value ||
                                 std::is_same<FixedSizeBinaryType, T>::value>;

    template<typename T, typename R = void>
    using enable_if_has_string_view = enable_if_t<has_string_view<T>::value, R>;

    template<typename T>
    using is_8bit_int = std::integral_constant<bool, std::is_same<UInt8Type, T>::value ||
                                                     std::is_same<Int8Type, T>::value>;

    template<typename T, typename R = void>
    using enable_if_8bit_int = enable_if_t<is_8bit_int<T>::value, R>;

    template<typename T>
    using is_parameter_free_type =
    std::integral_constant<bool, TypeTraits<T>::is_parameter_free>;

    template<typename T, typename R = void>
    using enable_if_parameter_free = enable_if_t<is_parameter_free_type<T>::value, R>;

    // Physical representation quirks

    template<typename T>
    using is_physical_signed_integer_type =
    std::integral_constant<bool,
        is_signed_integer_type<T>::value ||
        (is_temporal_type<T>::value && has_c_type<T>::value &&
         std::is_integral<typename T::c_type>::value)>;

    template<typename T, typename R = void>
    using enable_if_physical_signed_integer =
    enable_if_t<is_physical_signed_integer_type<T>::value, R>;

    template<typename T>
    using is_physical_unsigned_integer_type =
    std::integral_constant<bool, is_unsigned_integer_type<T>::value ||
                                 is_half_float_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_physical_unsigned_integer =
    enable_if_t<is_physical_unsigned_integer_type<T>::value, R>;

    template<typename T>
    using is_physical_integer_type =
    std::integral_constant<bool, is_physical_unsigned_integer_type<T>::value ||
                                 is_physical_signed_integer_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_physical_integer = enable_if_t<is_physical_integer_type<T>::value, R>;

    // Like is_floating_type but excluding half-floats which don't have a
    // float-like c type.
    template<typename T>
    using is_physical_floating_type =
    std::integral_constant<bool,
        is_floating_type<T>::value && !is_half_float_type<T>::value>;

    template<typename T, typename R = void>
    using enable_if_physical_floating_point =
    enable_if_t<is_physical_floating_type<T>::value, R>;

    /// @}

    /// \addtogroup runtime-type-predicates
    /// @{

    /// \brief Check for an integer type (signed or unsigned)
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is an integer type one
    constexpr bool is_integer(Type::type type_id) {
        switch (type_id) {
            case Type::UINT8:
            case Type::INT8:
            case Type::UINT16:
            case Type::INT16:
            case Type::UINT32:
            case Type::INT32:
            case Type::UINT64:
            case Type::INT64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a signed integer type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a signed integer type one
    constexpr bool is_signed_integer(Type::type type_id) {
        switch (type_id) {
            case Type::INT8:
            case Type::INT16:
            case Type::INT32:
            case Type::INT64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for an unsigned integer type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is an unsigned integer type one
    constexpr bool is_unsigned_integer(Type::type type_id) {
        switch (type_id) {
            case Type::UINT8:
            case Type::UINT16:
            case Type::UINT32:
            case Type::UINT64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a floating point type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a floating point type one
    constexpr bool is_floating(Type::type type_id) {
        switch (type_id) {
            case Type::FP16:
            case Type::FP32:
            case Type::FP64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a numeric type
    ///
    /// This predicate doesn't match decimals (see `is_decimal`).
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a numeric type one
    constexpr bool is_numeric(Type::type type_id) {
        switch (type_id) {
            case Type::UINT8:
            case Type::INT8:
            case Type::UINT16:
            case Type::INT16:
            case Type::UINT32:
            case Type::INT32:
            case Type::UINT64:
            case Type::INT64:
            case Type::FP16:
            case Type::FP32:
            case Type::FP64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a decimal type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a decimal type one
    constexpr bool is_decimal(Type::type type_id) {
        switch (type_id) {
            case Type::DECIMAL128:
            case Type::DECIMAL256:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a type that can be used as a run-end in Run-End Encoded
    /// arrays
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id can represent a run-end value
    constexpr bool is_run_end_type(Type::type type_id) {
        switch (type_id) {
            case Type::INT16:
            case Type::INT32:
            case Type::INT64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a primitive type
    ///
    /// This predicate doesn't match null, decimals and binary-like types.
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a primitive type one
    constexpr bool is_primitive(Type::type type_id) {
        switch (type_id) {
            case Type::BOOL:
            case Type::UINT8:
            case Type::INT8:
            case Type::UINT16:
            case Type::INT16:
            case Type::UINT32:
            case Type::INT32:
            case Type::UINT64:
            case Type::INT64:
            case Type::FP16:
            case Type::FP32:
            case Type::FP64:
            case Type::DATE32:
            case Type::DATE64:
            case Type::TIME32:
            case Type::TIME64:
            case Type::TIMESTAMP:
            case Type::DURATION:
            case Type::INTERVAL_MONTHS:
            case Type::INTERVAL_MONTH_DAY_NANO:
            case Type::INTERVAL_DAY_TIME:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a base-binary-like type
    ///
    /// This predicate doesn't match fixed-size binary types and will otherwise
    /// match all binary- and string-like types regardless of offset width.
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a base-binary-like type one
    constexpr bool is_base_binary_like(Type::type type_id) {
        switch (type_id) {
            case Type::BINARY:
            case Type::LARGE_BINARY:
            case Type::STRING:
            case Type::LARGE_STRING:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a binary-like type (i.e. with 32-bit offsets)
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a binary-like type one
    constexpr bool is_binary_like(Type::type type_id) {
        switch (type_id) {
            case Type::BINARY:
            case Type::STRING:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a large-binary-like type (i.e. with 64-bit offsets)
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a large-binary-like type one
    constexpr bool is_large_binary_like(Type::type type_id) {
        switch (type_id) {
            case Type::LARGE_BINARY:
            case Type::LARGE_STRING:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a binary (non-string) type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a binary type one
    constexpr bool is_binary(Type::type type_id) {
        switch (type_id) {
            case Type::BINARY:
            case Type::LARGE_BINARY:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a string type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a string type one
    constexpr bool is_string(Type::type type_id) {
        switch (type_id) {
            case Type::STRING:
            case Type::LARGE_STRING:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a temporal type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a temporal type one
    constexpr bool is_temporal(Type::type type_id) {
        switch (type_id) {
            case Type::DATE32:
            case Type::DATE64:
            case Type::TIME32:
            case Type::TIME64:
            case Type::TIMESTAMP:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a time type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a primitive type one
    constexpr bool is_time(Type::type type_id) {
        switch (type_id) {
            case Type::TIME32:
            case Type::TIME64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a date type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a primitive type one
    constexpr bool is_date(Type::type type_id) {
        switch (type_id) {
            case Type::DATE32:
            case Type::DATE64:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for an interval type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is an interval type one
    constexpr bool is_interval(Type::type type_id) {
        switch (type_id) {
            case Type::INTERVAL_MONTHS:
            case Type::INTERVAL_DAY_TIME:
            case Type::INTERVAL_MONTH_DAY_NANO:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a dictionary type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a dictionary type one
    constexpr bool is_dictionary(Type::type type_id) { return type_id == Type::DICTIONARY; }

    /// \brief Check for a fixed-size-binary type
    ///
    /// This predicate also matches decimals.
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a fixed-size-binary type one
    constexpr bool is_fixed_size_binary(Type::type type_id) {
        switch (type_id) {
            case Type::DECIMAL128:
            case Type::DECIMAL256:
            case Type::FIXED_SIZE_BINARY:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a fixed-width type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a fixed-width type one
    constexpr bool is_fixed_width(Type::type type_id) {
        return is_primitive(type_id) || is_dictionary(type_id) || is_fixed_size_binary(type_id);
    }

    /// \brief Check for a variable-length list type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a variable-length list type one
    constexpr bool is_var_length_list(Type::type type_id) {
        switch (type_id) {
            case Type::LIST:
            case Type::LARGE_LIST:
            case Type::MAP:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a list type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a list type one
    constexpr bool is_list(Type::type type_id) {
        switch (type_id) {
            case Type::LIST:
            case Type::LARGE_LIST:
            case Type::FIXED_SIZE_LIST:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a list-like type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a list-like type one
    constexpr bool is_list_like(Type::type type_id) {
        switch (type_id) {
            case Type::LIST:
            case Type::LARGE_LIST:
            case Type::FIXED_SIZE_LIST:
            case Type::MAP:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a var-length list or list-view like type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a var-length list or list-view like type
    constexpr bool is_var_length_list_like(Type::type type_id) {
        switch (type_id) {
            case Type::LIST:
            case Type::LARGE_LIST:
            case Type::LIST_VIEW:
            case Type::LARGE_LIST_VIEW:
            case Type::MAP:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a list-view type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a list-view type one
    constexpr bool is_list_view(Type::type type_id) {
        switch (type_id) {
            case Type::LIST_VIEW:
            case Type::LARGE_LIST_VIEW:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a nested type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a nested type one
    constexpr bool is_nested(Type::type type_id) {
        switch (type_id) {
            case Type::LIST:
            case Type::LARGE_LIST:
            case Type::LIST_VIEW:
            case Type::LARGE_LIST_VIEW:
            case Type::FIXED_SIZE_LIST:
            case Type::MAP:
            case Type::STRUCT:
            case Type::SPARSE_UNION:
            case Type::DENSE_UNION:
            case Type::RUN_END_ENCODED:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Check for a union type
    ///
    /// \param[in] type_id the type-id to check
    /// \return whether type-id is a union type one
    constexpr bool is_union(Type::type type_id) {
        switch (type_id) {
            case Type::SPARSE_UNION:
            case Type::DENSE_UNION:
                return true;
            default:
                break;
        }
        return false;
    }

    /// \brief Return the values bit width of a type
    ///
    /// \param[in] type_id the type-id to check
    /// \return the values bit width, or 0 if the type does not have fixed-width values
    ///
    /// For Type::FIXED_SIZE_BINARY, you will instead need to inspect the concrete
    /// DataType to get this information.
    static inline int bit_width(Type::type type_id) {
        switch (type_id) {
            case Type::BOOL:
                return 1;
            case Type::UINT8:
            case Type::INT8:
                return 8;
            case Type::UINT16:
            case Type::INT16:
                return 16;
            case Type::UINT32:
            case Type::INT32:
            case Type::DATE32:
            case Type::TIME32:
                return 32;
            case Type::UINT64:
            case Type::INT64:
            case Type::DATE64:
            case Type::TIME64:
            case Type::TIMESTAMP:
            case Type::DURATION:
                return 64;

            case Type::FP16:
                return 16;
            case Type::FP32:
                return 32;
            case Type::FP64:
                return 64;

            case Type::INTERVAL_MONTHS:
                return 32;
            case Type::INTERVAL_DAY_TIME:
                return 64;
            case Type::INTERVAL_MONTH_DAY_NANO:
                return 128;

            case Type::DECIMAL128:
                return 128;
            case Type::DECIMAL256:
                return 256;

            default:
                break;
        }
        return 0;
    }

    /// \brief Return the offsets bit width of a type
    ///
    /// \param[in] type_id the type-id to check
    /// \return the offsets bit width, or 0 if the type does not have offsets
    static inline int offset_bit_width(Type::type type_id) {
        switch (type_id) {
            case Type::STRING:
            case Type::BINARY:
            case Type::LIST:
            case Type::LIST_VIEW:
            case Type::MAP:
            case Type::DENSE_UNION:
                return 32;
            case Type::LARGE_STRING:
            case Type::LARGE_BINARY:
            case Type::LARGE_LIST:
            case Type::LARGE_LIST_VIEW:
                return 64;
            default:
                break;
        }
        return 0;
    }

    /// \brief Get the alignment a buffer should have to be considered "value aligned"
    ///
    /// Some buffers are frequently type-punned.  For example, in an int32 array the
    /// values buffer is frequently cast to int32_t*
    ///
    /// This sort of punning is technically only valid if the pointer is aligned to a
    /// proper width (e.g. 4 bytes in the case of int32).  However, most modern compilers
    /// are quite permissive if we get this wrong.  Note that this alignment is something
    /// that is guaranteed by malloc (e.g. new int32_t[] will return a buffer that is 4
    /// byte aligned) or common libraries (e.g. numpy) but it is not currently guaranteed
    /// by flight (GH-32276).
    ///
    /// We call this "value aligned" and this method will calculate that required alignment.
    ///
    /// \param type_id the type of the array containing the buffer
    ///                Note: this should be the indices type for a dictionary array since
    ///                A dictionary array's buffers are indices.  It should be the storage
    ///                type for an extension array.
    /// \param buffer_index the index of the buffer to check, for example 0 will typically
    ///                     give you the alignment expected of the validity buffer
    /// \return the required value alignment in bytes (1 if no alignment required)
    int RequiredValueAlignmentForBuffer(Type::type type_id, int buffer_index);

    /// \brief Check for an integer type (signed or unsigned)
    ///
    /// \param[in] type the type to check
    /// \return whether type is an integer type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_integer(const DataType &type) { return is_integer(type.id()); }

    /// \brief Check for a signed integer type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a signed integer type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_signed_integer(const DataType &type) {
        return is_signed_integer(type.id());
    }

    /// \brief Check for an unsigned integer type
    ///
    /// \param[in] type the type to check
    /// \return whether type is an unsigned integer type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_unsigned_integer(const DataType &type) {
        return is_unsigned_integer(type.id());
    }

    /// \brief Check for a floating point type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a floating point type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_floating(const DataType &type) { return is_floating(type.id()); }

    /// \brief Check for a numeric type (number except boolean type)
    ///
    /// \param[in] type the type to check
    /// \return whether type is a numeric type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_numeric(const DataType &type) { return is_numeric(type.id()); }

    /// \brief Check for a decimal type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a decimal type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_decimal(const DataType &type) { return is_decimal(type.id()); }

    /// \brief Check for a primitive type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a primitive type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_primitive(const DataType &type) { return is_primitive(type.id()); }

    /// \brief Check for a binary or string-like type (except fixed-size binary)
    ///
    /// \param[in] type the type to check
    /// \return whether type is a binary or string-like type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_base_binary_like(const DataType &type) {
        return is_base_binary_like(type.id());
    }

    /// \brief Check for a binary-like type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a binary-like type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_binary_like(const DataType &type) {
        return is_binary_like(type.id());
    }

    /// \brief Check for a large-binary-like type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a large-binary-like type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_large_binary_like(const DataType &type) {
        return is_large_binary_like(type.id());
    }

    /// \brief Check for a binary type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a binary type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_binary(const DataType &type) { return is_binary(type.id()); }

    /// \brief Check for a string type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a string type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_string(const DataType &type) { return is_string(type.id()); }

    /// \brief Check for a temporal type, including time and timestamps for each unit
    ///
    /// \param[in] type the type to check
    /// \return whether type is a temporal type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_temporal(const DataType &type) { return is_temporal(type.id()); }

    /// \brief Check for an interval type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a interval type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_interval(const DataType &type) { return is_interval(type.id()); }

    /// \brief Check for a dictionary type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a dictionary type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_dictionary(const DataType &type) {
        return is_dictionary(type.id());
    }

    /// \brief Check for a fixed-size-binary type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a fixed-size-binary type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_fixed_size_binary(const DataType &type) {
        return is_fixed_size_binary(type.id());
    }

    /// \brief Check for a fixed-width type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a fixed-width type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_fixed_width(const DataType &type) {
        return is_fixed_width(type.id());
    }

    /// \brief Check for a variable-length list type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a variable-length list type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_var_length_list(const DataType &type) {
        return is_var_length_list(type.id());
    }

    /// \brief Check for a list-like type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a list-like type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_list_like(const DataType &type) { return is_list_like(type.id()); }

    /// \brief Check for a var-length list or list-view like type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a var-length list or list-view like type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_var_length_list_like(const DataType &type) {
        return is_var_length_list_like(type.id());
    }

    /// \brief Check for a list-view type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a list-view type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_list_view(const DataType &type) { return is_list_view(type.id()); }

    /// \brief Check for a nested type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a nested type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_nested(const DataType &type) { return is_nested(type.id()); }

    /// \brief Check for a union type
    ///
    /// \param[in] type the type to check
    /// \return whether type is a union type
    ///
    /// Convenience for checking using the type's id
    static inline bool is_union(const DataType &type) { return is_union(type.id()); }

    /// @}
} // namespace nebula
